package com.h819.core.net.ftp;

import it.sauronsoftware.ftp4j.FTPAbortedException;
import it.sauronsoftware.ftp4j.FTPClient;
import it.sauronsoftware.ftp4j.FTPDataTransferException;
import it.sauronsoftware.ftp4j.FTPException;
import it.sauronsoftware.ftp4j.FTPFile;
import it.sauronsoftware.ftp4j.FTPIllegalReplyException;
import it.sauronsoftware.ftp4j.FTPListParseException;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import org.apache.commons.io.FilenameUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;

import com.h819.core.commons.Configurations;
import com.h819.core.db.MyDbUtils;
import com.h819.core.file.MyFileUtils;

/**
 * @Title: Ftp4jUtils.java
 * @Description: TODO(用来扩展 ftp4j FTPClient 类提供的方法)
 * @author H819
 * @date 2010-3-20
 * @version V1.0
 */

/*
 * 注意事项：
 * 
 * 不同的异常为不同的连接异常情况(具体查看其 api)，上层函数调用的时候，可以区分情况，灵活分别处理，所以把异常抛出到上层.
 * 
 * 
 * 	如果涉及递归操作，应把把ftp服务器连接(即获取 FTPClient)放入类变量，不要放入递归函数中，避免反复创建连接。
 * 
 *  用后，服务器关闭操作应该单独操作。
 * 
 * 
 * 如果应用到了 client.currentDirectory() 方法，那么传递进来的 client 参数的当前目录就会发生变化了。为了保持 client
 * 属性不变，需要把其当前目录变回原始状态。
 */

/**
 * 本类只是 FTPClient 类的扩展，实际使用时，应该和 FTPClient 一起使用。
 */

public class Ftp4jUtils {

	private Logger log = Logger.getLogger(Ftp4jUtils.class);

	// static FTPClient client = null;

	// 仅判断匹配:
	// 1. "/" 开头，"/" 结尾
	// 2. "/" 根目录
	private String ftpDerictoryURLregexp = "^/.+/$|/";
	// 仅判断匹配 "/" 开头，非"/" 结尾，并且至少有一个字符
	private String ftpFileURLregexp = "^/.+[^/]$";

	/**
	 * 
	 * 静态类，提供静态方法，不允许实例化
	 * 
	 */
	public Ftp4jUtils() {
	}

	/**
	 * 获得 FTPClient,编码为 GBK,传输模式为 FTPClient.TYPE_BINARY.
	 * 
	 * 如果匿名访问，用户名为 "anonymous", 密码任意，但有的 ftp 仅接受一个邮件地址 ，如 "ftp4j@mail.com"（详见官方
	 * API）
	 * 
	 * 不同的异常为不同的连接异常情况(具体查看其 api)，把异常抛给上层函数调用，可以区分情况，灵活分别处理.
	 * 
	 * @param hostIp
	 * @param port
	 * @param username
	 * @param password
	 * @return
	 * @throws FTPException
	 * @throws FTPIllegalReplyException
	 * @throws IOException
	 * @throws IllegalStateException
	 */
	public FTPClient getFTPClient(String hostIp, int port, String username,
			String password) throws IllegalStateException, IOException,
			FTPIllegalReplyException, FTPException {
		FTPClient client = new FTPClient();
		// 连接到指定的FTP服务器(域名或IP) 不指定端口，则使用默认端口21

		client.connect(hostIp, port);
		client.setCharset("GBK");
		client.setType(FTPClient.TYPE_BINARY);
		client.login(username, password);

		return client;
	}

	/**
	 * 下载服务器当前目录中所有文件到本地目录下。不能断点续传，直接覆盖。
	 * 
	 * 不同的异常为不同的连接异常情况(具体查看其 api)，上层函数调用的时候，可以区分情况，灵活分别处理.
	 * 
	 * @param client
	 * @param remoteDirectory
	 *            远程目录
	 * @param localDirectory
	 *            本地目录
	 * @throws FTPListParseException
	 * @throws FTPAbortedException
	 * @throws FTPDataTransferException
	 * @throws FTPException
	 * @throws FTPIllegalReplyException
	 * @throws IOException
	 * @throws IllegalStateException
	 */
	public void downLoadDirectory(FTPClient client, String remoteDirectory,
			String localDirectory) throws IllegalStateException, IOException,
			FTPIllegalReplyException, FTPException, FTPDataTransferException,
			FTPAbortedException, FTPListParseException {

		downLoadDirectory(client, remoteDirectory, localDirectory, null, null,
				false);

	}

	/**
	 * 下载服务器目录中，到本地目录下，本地自动创建目录结构。文件名称(不包括目录部分)在指定文件列表中。不能断点续传，直接覆盖。
	 * 
	 * 不同的异常为不同的连接异常情况(具体查看其 api)，上层函数调用的时候，可以区分情况，灵活分别处理.
	 * 
	 * @param client
	 * 
	 * @param remoteDirectory
	 *            远程目录要下载的根目录
	 * @param localDirectory
	 *            本地目录根目录
	 * @param fileNamesList
	 *            要下载的文件列名称列表
	 * @param fileExtensionFilter
	 *            文件扩展名过滤器 ,大小写敏感,如 String[] fileExtensionFilter = {
	 *            "java","pdf","PDF" }</br> null 为指定的所有文件
	 * 
	 * @param recursivelySubDirectory
	 *            是否递归下载子文件夹中包含在文件列表中的文件
	 * @throws FTPException
	 * @throws FTPIllegalReplyException
	 * @throws IOException
	 * @throws IllegalStateException
	 * @throws FTPListParseException
	 * @throws FTPAbortedException
	 * @throws FTPDataTransferException
	 */
	public void downLoadDirectory(FTPClient client, String remoteDirectory,
			String localDirectory, List<String> fileNamesList,
			String[] fileExtensionFilter, boolean recursivelySubDirectory)
			throws IllegalStateException, IOException,
			FTPIllegalReplyException, FTPException, FTPDataTransferException,
			FTPAbortedException, FTPListParseException {

		if (!exsitsDirectory(client, remoteDirectory))
			throw new IOException("remoteDirectory '" + remoteDirectory
					+ "' not a ftp path.");

		File localDesc = new File(localDirectory);

		// 记录原始 client 当前目录
		String currentDirectory = client.currentDirectory();

		// 创建本地文件夹，如果存在，则略过
		MyFileUtils.forceMkdir(localDesc);

		client.changeDirectory(remoteDirectory);
		// list 方法可以用文件名作为过滤器
		FTPFile[] fileList = client.list();

		for (FTPFile f : fileList) {

			if (f.getType() == FTPFile.TYPE_FILE) {

				// 远程文件路径，用工具规范化
				String remoteFilePath = FilenameUtils
						.separatorsToUnix(remoteDirectory + f.getName());
				// 本地文件路径，用工具规范化
				String localFilePath = FilenameUtils
						.separatorsToSystem(localDirectory + File.separator
								+ f.getName());

				// 是否在过滤目录中
				if (fileExtensionFilter != null
						&& !FilenameUtils.isExtension(remoteFilePath,
								fileExtensionFilter))
					continue;

				// 是否在指定的文件列表中
				if (fileNamesList != null
						&& !fileNamesList.contains(f.getName()))
					continue;

				log.info("begin file copy :" + remoteFilePath + " to "
						+ localFilePath);

				client.download(remoteFilePath, new File(localFilePath));

				// log.info("end file copy :" + remoteFilePath + " to "
				// + localFilePath);
			}

			if (f.getType() == FTPFile.TYPE_DIRECTORY) {

				// 不递归下载子文件夹
				if (!recursivelySubDirectory)
					continue;

				// 跳过目录中的 ". " 和 ".." (根目录)
				if (f.getName().equals(".") || f.getName().equals(".."))
					continue;

				// 远程文件路径，用工具规范化。一般 ftp 目录都是 Unix 路径模式
				String remoteFilePath = FilenameUtils
						.separatorsToUnix(remoteDirectory + f.getName() + "/");
				// 本地文件路径，用工具规范化
				String localFilePath = FilenameUtils
						.separatorsToSystem(localDirectory + File.separator
								+ f.getName() + File.separator);

				// 跳过空文件夹
				if (this.isEmptyFolder(client, remoteFilePath))
					continue;

				log.info("begin directory copy :" + remoteFilePath + " to "
						+ localFilePath);

				downLoadDirectory(client, remoteFilePath, localFilePath,
						fileNamesList, fileExtensionFilter,
						recursivelySubDirectory);
				//
				// log.info("end directory copy :" + remoteFilePath + " to "
				// + localFilePath);

			}
		}
		// client 的根目录还原为原始状态
		client.changeDirectory(currentDirectory);
	}

	/**
	 * 下载远程的文件到输出流
	 * 
	 * 不同的异常为不同的连接异常情况(具体查看其 api)，上层函数调用的时候，可以区分情况，灵活分别处理.
	 * 
	 * @param client
	 * @param remoteFileName
	 *            The name of the remote file
	 * @param outputStream
	 *            The destination stream of data read during the download.
	 * @throws FTPAbortedException
	 * @throws FTPDataTransferException
	 * @throws FTPException
	 * @throws FTPIllegalReplyException
	 * @throws IOException
	 * @throws IllegalStateException
	 */
	public void downLoadFile(FTPClient client, java.lang.String remoteFileName,
			java.io.OutputStream outputStream) throws IllegalStateException,
			IOException, FTPIllegalReplyException, FTPException,
			FTPDataTransferException, FTPAbortedException {

		client.download(remoteFileName, outputStream, 0, null);

	}

	/**
	 * 批量下载指定文件名称的文件到本地文件夹中
	 * 
	 * 不同的异常为不同的连接异常情况(具体查看其 api)，上层函数调用的时候，可以区分情况，灵活分别处理.
	 * 
	 * @param client
	 * @param fileNameList
	 *            ftp 服务器上面的文件列表
	 * @param localDirectory
	 *            本地文件夹
	 * @throws FTPAbortedException
	 * @throws FTPDataTransferException
	 * @throws FTPException
	 * @throws FTPIllegalReplyException
	 * @throws IOException
	 * @throws FileNotFoundException
	 * @throws IllegalStateException
	 */
	public void downLoadFile(FTPClient client, List<String> fileNameList,
			String localDirectory) throws IllegalStateException,
			FileNotFoundException, IOException, FTPIllegalReplyException,
			FTPException, FTPDataTransferException, FTPAbortedException {

		for (String name : fileNameList)
			client.download(name, new File(localDirectory + "//" + name));

	}

	/**
	 * 把指定本地目录中的文件(包含子目录中的文件)，上传到服务器指定目录下,自动建立相应的目录结构
	 * 
	 * 不同的异常为不同的连接异常情况(具体查看其 api)，上层函数调用的时候，可以区分情况，灵活分别处理.
	 * 
	 * @param client
	 * @param remoteDirectory
	 *            远程目录
	 * @param localDirectory
	 *            本地目录
	 * @throws FTPAbortedException
	 * @throws FTPDataTransferException
	 * @throws FTPException
	 * @throws FTPIllegalReplyException
	 * @throws IOException
	 * @throws IllegalStateException
	 */
	public void upLoadDirectory(FTPClient client, String localDirectory,
			String remoteDirectory) throws IllegalStateException, IOException,
			FTPIllegalReplyException, FTPException, FTPDataTransferException,
			FTPAbortedException {

		upLoadDirectory(client, localDirectory, remoteDirectory, null, null);

	}

	/**
	 * 把指定本地目录中的文件(包含子目录中的文件)，上传到服务器指定目录下，并自动建立目录结构
	 * 
	 * 不同的异常为不同的连接异常情况(具体查看其 api)，上层函数调用的时候，可以区分情况，灵活分别处理.
	 * 
	 * @param client
	 * 
	 * @param remoteDirectory
	 *            远程目录
	 * @param localDirectory
	 *            本地目录
	 * @param fileNamesList
	 *            要下载的文件列表
	 * @param fileExtensionFilter
	 *            文件扩展名过滤器 ,大小写敏感,如 String[] fileExtensionFilter = {
	 *            "java","pdf","PDF" };
	 * @throws FTPException
	 * @throws FTPIllegalReplyException
	 * @throws IOException
	 * @throws IllegalStateException
	 * @throws FTPAbortedException
	 * @throws FTPDataTransferException
	 */
	public void upLoadDirectory(FTPClient client, String localDirectory,
			String remoteDirectory, List<String> fileNamesList,
			String[] fileExtensionFilter) throws IllegalStateException,
			IOException, FTPIllegalReplyException, FTPException,
			FTPDataTransferException, FTPAbortedException {

		remoteDirectory = FilenameUtils.separatorsToUnix(remoteDirectory);
		localDirectory = FilenameUtils.separatorsToSystem(localDirectory);

		// log.info("remoteDirectory : " + remoteDirectory);
		// log.info("localDirectory : " + localDirectory);

		if (!isFTPDirectoryURL(remoteDirectory))
			throw new IOException("remoteDirectory '" + remoteDirectory
					+ "' is not a directory url.");

		File localSrc = new File(localDirectory);
		if (localDirectory != null && !localSrc.isDirectory())
			throw new IOException("localDirectory '" + localDirectory
					+ "' is not a Directory url.");

		File[] fileList = localSrc.listFiles();

		// 记录原始 client 当前目录
		String currentDirectory = client.currentDirectory();

		for (File f : fileList) {

			// log.info("f.getName() :" + f.getName());

			if (f.isFile()) {

				// 是否在过滤目录中
				if (fileExtensionFilter != null
						&& !FilenameUtils.isExtension(f.getName(),
								fileExtensionFilter))
					continue;

				// 是否在指定的文件列表中
				if (fileNamesList != null
						&& !fileNamesList.contains(f.getName()))
					continue;

				if (!this.exsitsDirectory(client, remoteDirectory)) {
					client.createDirectory(remoteDirectory);
					client.changeDirectory(remoteDirectory);
					// 上传到当前目录
					client.upload(f);
					log.info(remoteDirectory + f.getName() + " uploaded .");
				} else {
					client.changeDirectory(remoteDirectory);
					client.upload(f);
					log.info(remoteDirectory + f.getName() + " uploaded .");
				}
			}

			if (f.isDirectory()) {

				if (!this.exsitsDirectory(client, remoteDirectory)) {

					// 调用本类的创建目录的增强方法，创建目录
					this.createDirectory(client, remoteDirectory);
					client.changeDirectory(remoteDirectory);
					client.createDirectory(f.getName());
					client.changeDirectory(remoteDirectory + "/" + f.getName()
							+ "/");

				} else {

					client.changeDirectory(remoteDirectory);
					client.createDirectory(f.getName());
					client.changeDirectory(remoteDirectory + "/" + f.getName()
							+ "/");
				}

				upLoadDirectory(client, f.getCanonicalPath(),
						client.currentDirectory() + "/", fileNamesList,
						fileExtensionFilter);
			}

		}

		// client 的根目录还原为原始状态
		client.changeDirectory(currentDirectory);

	}

	/**
	 * 递归建立目录结构
	 * 
	 * 不同的异常为不同的连接异常情况(具体查看其 api)，上层函数调用的时候，可以区分情况，灵活分别处理.
	 * 
	 * @param client
	 * @param remoteDirectory
	 *            远程目录结构，如给定目录结构 /first/second/three/，则会在服务器上面建立相应的目录结构
	 * @throws FTPException
	 * @throws FTPIllegalReplyException
	 * @throws IOException
	 * @throws IllegalStateException
	 * @throws IOException
	 */
	public void createDirectory(FTPClient client, String remoteDirectory)
			throws IllegalStateException, IOException,
			FTPIllegalReplyException, FTPException {

		if (!isFTPDirectoryURL(remoteDirectory))
			throw new IOException("remoteDirectory '" + remoteDirectory
					+ "' is not a directory url.");

		String[] token = StringUtils.split(remoteDirectory, '/');

		// 记录原始 client 当前目录
		String currentDirectory = client.currentDirectory();

		String path = "";
		for (String s : token) {
			path = FilenameUtils.separatorsToUnix(path + "/" + s + "/");
			if (!this.exsitsDirectory(client, path)) {
				client.createDirectory(path);
				// log.info("path :" + path);
				client.changeDirectory(path);
			}
		}

		// client 的根目录还原为原始状态
		client.changeDirectory(currentDirectory);

	}

	/**
	 * 关闭连接，不抛出异常
	 * 
	 * @param client
	 */
	public void disconnectQuietly(FTPClient client) {

		try {
			client.disconnect(true);
		} catch (IllegalStateException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (FTPIllegalReplyException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (FTPException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

	/**
	 * 判断给定的文件或者文件夹是否存在
	 * 
	 * @param client
	 * @param remotePath
	 *            远程的文件或者文件夹名称
	 * @return
	 * @throws IOException
	 */
	public boolean exists(FTPClient client, String remotePath)
			throws IOException {
		return this.existsFile(client, remotePath)
				|| this.exsitsDirectory(client, remotePath);
	}

	/**
	 * 判断给定的文件是否存在 ，用 list 方法来判断。
	 * 
	 * @param client
	 * @param remoteFile
	 *            远程的文件名称
	 * @return
	 * @throws IOException
	 */
	public boolean existsFile(FTPClient client, String remoteFile)
			throws IOException {

		if (!isFTPFileURL(remoteFile))
			throw new IOException("remoteFile '" + remoteFile
					+ "' is not a file url.");

		// 如果给定的路径不存在，会抛出异常
		try {

			FTPFile[] flist = client.list(remoteFile);
			if (flist.length == 1)
				return true;
			else
				return false;

			// for(FTPFile f : flist)
			//
			// out.println(f.getName());

			// fileSize()函数个别系统会有异常
			// client.fileSize(remoteFile); ;

		} catch (Exception e) {
			// TODO Auto-generated catch block
			// e.printStackTrace();
			return false;
		}
	}

	/**
	 * 判断给定的文件夹是否存在。利用 changeDirectory 方法是否成功来判断
	 * 
	 * @param client
	 * @param remoteDirectory
	 *            远程的文件夹名称
	 * @return
	 * @throws IOException
	 */
	public boolean exsitsDirectory(FTPClient client, String remoteDirectory)
			throws IOException {

		if (!isFTPDirectoryURL(remoteDirectory))
			throw new IOException("remoteDirectory '" + remoteDirectory
					+ "' is not a directory url.");

		try {

			// 为了不改变 client 的状态

			// 记录原始 client 当前目录
			String currentDirectory = client.currentDirectory();
			client.changeDirectory(remoteDirectory);
			// client 的根目录还原为原始状态
			client.changeDirectory(currentDirectory);

			return true;
		} catch (Exception e) {
			// TODO Auto-generated catch block
			// e.printStackTrace();
			return false;
		}
	}

	/**
	 * 判断给定的目录是否为空文件夹
	 * 
	 * @param client
	 * @param remoteDirectory
	 *            远程目录
	 * @return
	 * @throws IOException
	 */
	public boolean isEmptyFolder(FTPClient client, String remoteDirectory)
			throws IOException {

		if (!this.exsitsDirectory(client, remoteDirectory))
			throw new IOException("remoteDirectory '" + remoteDirectory
					+ "' does not exsit.");

		try {
			// 记录原始 client 当前目录
			String currentDirectory = client.currentDirectory();

			client.changeDirectory(remoteDirectory);
			FTPFile[] fileList = client.list();

			// client 的根目录还原为原始状态
			client.changeDirectory(currentDirectory);

			// 判断文件夹是否只存在 "." 和 "..". windows 系统是适用?
			return fileList.length == 2;

		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
			return false;
		}
	}

	/**
	 * 判断给定的字符串是否满足ftp文件路径要求。路径要求见正则表达式
	 * 
	 * @param remoteFTPFileURL
	 *            给定的文件路径
	 * @return
	 */
	private boolean isFTPFileURL(String remoteFTPFileURL) {

		return remoteFTPFileURL.matches(ftpFileURLregexp);

	}

	/**
	 * 判断给定的字符串是否满足ftp目录路径要求。路径要求见正则表达式
	 * 
	 * @param remoteFTPDirectoryURL
	 * @return
	 */
	private boolean isFTPDirectoryURL(String remoteFTPDirectoryURL) {

		return remoteFTPDirectoryURL.matches(ftpDerictoryURLregexp);

	}

	/**
	 * 演示用法
	 * 
	 * @throws IOException
	 * @throws FTPException
	 * @throws FTPIllegalReplyException
	 * @throws IllegalStateException
	 */
	private void example() throws IOException, IllegalStateException,
			FTPIllegalReplyException, FTPException {

		Ftp4jUtils ftpUtils = new Ftp4jUtils();

		FTPClient client = ftpUtils.getFTPClient("129.9.100.16", 21, "work",
				"work");

		// out.println(client.isFTPFileURL("/ab/c"));
		// out.println(client.isFTPFileURL("/ab/"));
		// out.println(client.isFTPFileURL("\\ab/c"));

		// out.println(client.isFTPFileURL("/001/33/java.pdf"));
		// client.existsFile(c, "/001/3/java.pd");
		// client.downLoad(c, "/download/commons-io-1.4-src/", "F:/036/");
		// out.println(File.separator);

		String[] extentions = { "pdf", "Pdf" };
		ArrayList<String> list = new ArrayList<String>();
		list.add("java.pdf");
		list.add("va.Pdf");

		// out.println("extention :"
		// + FilenameUtils.isExtension("f://a.pdf", extentions));

		// client.downLoad(c, "/001/", "F:\\036/", list, extentions, true);

		try {
			ftpUtils.upLoadDirectory(client, "F:/036", "/new2/36/63/");
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		// client.createDirectory(c, "/1/2.3/4/");

		// out.println(client.isEmptyFolder(c, "/001/33/3301/"));

		// 用后，
		ftpUtils.disconnectQuietly(client);

	}

	// 上传和下载，没有判断目标文件是否存在，直接覆盖

	/**
	 * @param args
	 * @throws FTPException
	 * @throws FTPIllegalReplyException
	 * @throws IOException
	 * @throws IllegalStateException
	 * @throws FTPException
	 * @throws FTPIllegalReplyException
	 * @throws IOException
	 * @throws IllegalStateException
	 */
	public static void main(String[] args) throws IllegalStateException,
			IOException, FTPIllegalReplyException, FTPException {
		// TODO Auto-generated method stub

		// Ftp4jUtils ftp = new Ftp4jUtils();

		// FTPClient fc = ftp.getFTPClient("129.9.200.236", "read", "readpdf");
		// System.out.println(ftp.existsFile(fc, "/DB/DB11!T~372-2006.PDF"));

	}

}
